package org.ricks.dispatch;

import org.ricks.asm.*;
import org.ricks.common.Tuple;

import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author chenwei
 * @Description: 网络消息分发逻辑
 * @date 2022/11/3013:46
 */
public class DispatcherDump implements Opcodes {



    public static byte[] dump(List<Tuple> tupleList) throws Exception {
        Set<String> classNames =  tupleList.stream().map(tuple -> (String)tuple.get(1)).collect(Collectors.toSet()); //对象入口
        ClassWriter classWriter = new ClassWriter(0);
        FieldVisitor fieldVisitor;
        MethodVisitor methodVisitor;

        classWriter.visit(V17, ACC_PUBLIC | ACC_SUPER, "org/ricks/dispatch/Dispatcher", null, "java/lang/Object", null);
        classWriter.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC | ACC_FINAL | ACC_STATIC);

        initFieldVisitor(classNames,classWriter);

        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(17, label0);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitInsn(RETURN);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLocalVariable("this", "Lorg/ricks/dispatch/Dispatcher;", null, label0, label1, 0);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();
        }

        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC , "dispatcher", "(Lorg/ricks/common/Context;)V", "(Lorg/ricks/common/Context<Ljava/lang/Short;[Ljava/lang/Byte;>;)V", null);
            methodVisitor.visitParameter("context", 0);
            methodVisitor.visitCode();

            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(23, label0);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "org/ricks/common/Context", "getCmd", "()Ljava/lang/Object;", false);
            methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Short");
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);

            methodVisitor.visitVarInsn(ISTORE, 1);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(24, label1);
            methodVisitor.visitVarInsn(ILOAD, 1);

            tupleList = tupleList.stream().sorted(Comparator.comparing(tuple -> tuple.get(0))).collect(Collectors.toList()); //妈的，浪费老子那么多时间。switch 必须要从小到大排序不然生成的class字节码会进入 default 默认。fuck you
            int[] messageIds = new int[tupleList.size()];
            Label[] labels = new Label[tupleList.size()];
            for (int i = 0; i < tupleList.size(); i++) {
                labels[i] = new Label();
                messageIds[i] = Integer.valueOf(tupleList.get(i).get(0).toString());
            }
            Label defaultLabel  = new Label();
            Label gotoLabel  = new Label();
            methodVisitor.visitLookupSwitchInsn(defaultLabel, messageIds, labels);
            int currLineNum = 24;
            for (int i = 0; i < tupleList.size(); i++) {
                currLineNum += 1;
                createMethodFunction(methodVisitor,tupleList.get(i), labels[i],currLineNum,gotoLabel,i==0);
            }

            methodVisitor.visitLabel(defaultLabel);
            methodVisitor.visitLineNumber(currLineNum + 1, defaultLabel);
            methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "org/ricks/common/Context", "getCmd", "()Ljava/lang/Object;", false);
            methodVisitor.visitInvokeDynamicInsn("makeConcatWithConstants", "(Ljava/lang/Object;)Ljava/lang/String;", new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/StringConcatFactory", "makeConcatWithConstants", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;", false), new Object[]{"\u0001 \u672a\u77e5\u6307\u4ee4"});
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            methodVisitor.visitLabel(gotoLabel);
            methodVisitor.visitLineNumber(currLineNum + 5, gotoLabel);
            methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            methodVisitor.visitInsn(RETURN);
            Label label14 = new Label();
            methodVisitor.visitLabel(label14);
            methodVisitor.visitLocalVariable("context", "Lorg/ricks/common/Context;", "Lorg/ricks/common/Context<Ljava/lang/Short;[Ljava/lang/Byte;>;", label0, label14, 0);
            methodVisitor.visitLocalVariable("cmd", "S", null, label1, label14, 1);
            methodVisitor.visitMaxs(2, 2);
            methodVisitor.visitEnd();
        }

        {
            methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            methodVisitor.visitCode();
            createEnd(methodVisitor,classNames);

            methodVisitor.visitInsn(RETURN);
            methodVisitor.visitMaxs(1, 0);
            methodVisitor.visitEnd();
        }

        classWriter.visitEnd();

        return classWriter.toByteArray();
    }



    private static FieldVisitor initFieldVisitor(Set<String> classNames, ClassWriter classWriter) {
        FieldVisitor fieldVisitor = null;
        for (String className : classNames) {
            String objName = className.substring(className.lastIndexOf(".") + 1,className.length()).toLowerCase(Locale.ROOT);
            String cName = className.replace(".","/");
            fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, objName, "L"+cName+";", null, null);
            fieldVisitor.visitEnd();
        }
        return fieldVisitor;
    }

    private static void  createMethodFunction(MethodVisitor methodVisitor, Tuple tuple, Label label, int lineNum, Label gotoLabel,boolean isFirst) {
        String className = tuple.get(1).toString();
        String cName = className.replace(".","/");
        String objName = className.substring(className.lastIndexOf(".") + 1,className.length()).toLowerCase(Locale.ROOT);
        methodVisitor.visitLabel(label);
        methodVisitor.visitLineNumber(lineNum, label);
        if(isFirst) methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null); else methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
        methodVisitor.visitFieldInsn(GETSTATIC, "org/ricks/dispatch/Dispatcher", objName, "L"+cName+";");
        methodVisitor.visitVarInsn(ALOAD, 0);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, cName, tuple.get(2), "(Lorg/ricks/common/Context;)V", false);

//        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
//        methodVisitor.visitLdcInsn("\u8fdb\u5165\u539f\u751f\u6001\u3002\u3002\u3002\u3002\u3002\u3002\u3002\u3002");
//        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

        methodVisitor.visitJumpInsn(GOTO, gotoLabel);

    }

    private static void createEnd(MethodVisitor methodVisitor,Set<String> classNames) {
        int line = 19;
        for (String className : classNames) {
            String objName = className.substring(className.lastIndexOf(".") + 1,className.length()).toLowerCase(Locale.ROOT);
            String cName = className.replace(".","/");
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(line, label0);
            methodVisitor.visitLdcInsn(Type.getType("L"+cName+";"));
            methodVisitor.visitMethodInsn(INVOKESTATIC, "org/ricks/ioc/AppContext", "getBean", "(Ljava/lang/Class;)Ljava/lang/Object;", false);
            methodVisitor.visitTypeInsn(CHECKCAST, cName);
            methodVisitor.visitFieldInsn(PUTSTATIC, "org/ricks/dispatch/Dispatcher", objName, "L"+cName+";");
            line += 1;
        }

    }
}
